package cn.itcast.es.demo;

import cn.itcast.es.pojo.User;
import cn.itcast.es.service.UserService;
import com.alibaba.fastjson.JSON;
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

public class ElasticClassDemo {

    private RestHighLevelClient client;

    /**
     * 建立连接
     */
    @Before
    public void init() throws IOException {
        client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")
                )
        );
    }

    /**
     * 关闭客户端连接
     */
    @After
    public void close() throws IOException {
        client.close();
    }

    @Test
    public void testCreateIndex() throws IOException {

        // 1. 创建一个索引库
        CreateIndexRequest indexRequest = new CreateIndexRequest("heima2");

        // 2. 指定settings配置
        indexRequest.settings(Settings.builder()
                .put("index.number_of_shards",3)
                .put("index.number_of_replicas",1)
        );

        // 3. 指定mapping配置 这段映射可以直接将上面的properties后面的内容直接拷贝下来，直接就会变成json格式的数据
        indexRequest.mapping("{\n" +
                        "    \"properties\": {\n" +
                        "      \"id\": {\n" +
                        "        \"type\": \"long\"\n" +
                        "      },\n" +
                        "      \"name\":{\n" +
                        "        \"type\": \"keyword\"\n" +
                        "      },\n" +
                        "      \"age\":{\n" +
                        "        \"type\": \"integer\"\n" +
                        "      },\n" +
                        "      \"gender\":{\n" +
                        "        \"type\": \"keyword\"\n" +
                        "      },\n" +
                        "      \"note\":{\n" +
                        "        \"type\": \"text\",\n" +
                        "        \"analyzer\": \"ik_max_word\"\n" +
                        "      }\n" +
                        "    }\n" +
                        "}"
                , XContentType.JSON);

        // 4. 发起请求，得到响应
        CreateIndexResponse createIndexResponse = this.client.indices().create(indexRequest, RequestOptions.DEFAULT);

        // 输出响应结果
        System.out.println(createIndexResponse.isAcknowledged());

//        System.out.println(createIndexResponse.isShardsAcknowledged());

    }

    @Test
    public void testDeleteIndex() throws IOException {

        // 创建一个删除索引库对象
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("heima2");

        // 客户端发起删除索引库请求
        AcknowledgedResponse response = this.client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);

        // 输出响应结果
        System.out.println(response.isAcknowledged());

    }

    @Test
    public void testAddDocument() throws IOException {

        IndexRequest indexRequest = new IndexRequest("heima2");

        // id,lucene,数值存储转义，保证了数据的安全性，会对数据进行类似于加密的操作，传入字符串就不会存在这个问题
        // 传入int long 类型的数据都会加密，一加密下次就找不到了，就像我们之前使用MD5对密码进行加密一样
        indexRequest.id("10086");

        // 在idea中编写json格式的数据 Alt + insert(回车)，选择 inject language 选择 JSON，再按一次按回车 多了一个 EDIT JSON Fragment
        indexRequest.source("{\n" +
                "  \"id\": 10086,\n" +
                "  \"name\": \"黑马\",\n" +
                "  \"gender\": \"马\",\n" +
                "  \"age\": 20,\n" +
                "  \"note\": \"黑马程序员，你成长的摇篮\"\n" +
                "}",XContentType.JSON);

        IndexResponse indexResponse = this.client.index(indexRequest, RequestOptions.DEFAULT);

        System.out.println(indexResponse.status());

    }

    private UserService userService = new UserService();

    @Test
    public void testAddDocumentFromDB() throws IOException {

        User user = userService.findById(1L);

        // user序列化的json形式
        String jsonString = JSON.toJSONString(user);

        IndexRequest indexRequest = new IndexRequest("heima2");

        // id,lucene,数值存储转义 需要设置id,否则会自动生成id一长串
        indexRequest.id(String.valueOf(user.getId()));

        // 需要设置数据源，就相当于sql语句查询的数据源，但要指定JSON格式
        indexRequest.source(jsonString,XContentType.JSON);

        IndexResponse indexResponse = this.client.index(indexRequest, RequestOptions.DEFAULT);

        System.out.println(indexResponse.status());

    }

    @Test
    public void testAddDocumentFromDBBulk() throws IOException {

        //批量处理信息对象
        BulkRequest bulkRequest = new BulkRequest();

        // 查询所有的用户信息
        List<User> users = userService.findAll();

        //给bulkRequest添加每个子indexRequest请求
        users.forEach(user -> {

            IndexRequest indexRequest = new IndexRequest("heima2");

            indexRequest.id(String.valueOf(user.getId()));

            indexRequest.source(JSON.toJSONString(user),XContentType.JSON);

            bulkRequest.add(indexRequest);

        });

        // 执行批量加入请求
        BulkResponse bulkResponse = this.client.bulk(bulkRequest, RequestOptions.DEFAULT);

        System.out.println(bulkResponse.status());

    }

    @Test
    public void testQueryById() throws IOException {

        // 查询索引库为 heima2
        GetRequest getRequest = new GetRequest("heima2");

        // 指定查询id
        getRequest.id("10086");

        GetResponse getResponse = this.client.get(getRequest, RequestOptions.DEFAULT);

        // 从查询结果中获取到source，转变为string
        String userJson = getResponse.getSourceAsString();

        User user = JSON.parseObject(userJson, User.class);

        System.out.println(user);

    }

    @Test
    public void testQuery() throws IOException {

        SearchRequest searchRequest = new SearchRequest("heima2");

        // 条件构建工具，五大：查询 分页 高亮 聚合 排序 字段过滤
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 在条件构建工具中添加查询条件，分词查询
//        sourceBuilder.query(QueryBuilders.matchQuery("note","传智播客"));
        // 词条查询
        sourceBuilder.query(QueryBuilders.termQuery("note","java"));

        // 分页处理 起始索引为3，显示3条
        sourceBuilder.from(3);
        sourceBuilder.size(3);

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = this.client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits searchHits = searchResponse.getHits();

        // 命中个数
        long totalHits = searchHits.getTotalHits().value;

        System.out.println("本次查询命个数为：" + totalHits);

        // 实际查询到的元素集合
        SearchHit[] hits = searchHits.getHits();

        for (SearchHit hit : hits) {

            float score = hit.getScore();

            User user = JSON.parseObject(hit.getSourceAsString(), User.class);

            System.out.println("得分：" + score + " info: " + user);

        }

    }

    @Test
    public void testQueryHighLight() throws IOException {

        SearchRequest searchRequest = new SearchRequest("heima2");

        // 条件构建工具
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 在条件构建工具中添加查询条件 分词查询
        sourceBuilder.query(QueryBuilders.matchQuery("note","传智播客"));

        HighlightBuilder highlightBuilder = new HighlightBuilder();

        // 设置高亮的字段以及对应的标签
        highlightBuilder.preTags("<em>");
        highlightBuilder.postTags("</em>");
        highlightBuilder.field("note");

        // 添加高亮条件
        sourceBuilder.highlighter(highlightBuilder);

        // 添加条件
        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = this.client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits searchHits = searchResponse.getHits();

        // 命中个数
        long totalHits = searchHits.getTotalHits().value;

        // 实际查询到的元素集合
        SearchHit[] hits = searchHits.getHits();

        for (SearchHit hit : hits) {

            float score = hit.getScore();

            User user = JSON.parseObject(hit.getSourceAsString(), User.class);

            // 从查询结果中取出高亮信息
            Text[] notes = hit.getHighlightFields().get("note").fragments();

            // 高亮信息string化处理
//            String join = StringUtils.join(notes);
            String join = Arrays.toString(notes);

            // 手动把高亮的信息赋值给具体的高亮属性
            user.setNote(join);

            System.out.println("得分：" + score + " info: " + user);

        }

    }

    @Test
    public void testAggs() throws IOException {

        // 查询请求对象
        SearchRequest searchRequest = new SearchRequest("car");

        // 条件构建工具
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 不写条件默认查询所有，相当于下面的语句
//        SearchSourceBuilder query = sourceBuilder.query(QueryBuilders.matchAllQuery());

        // 不展示查询结果
        sourceBuilder.size(0);

        // 添加聚合，词条内容聚合和桶内度量
        sourceBuilder.aggregation(
                AggregationBuilders.terms("makes").field("make").size(10)
                .subAggregation(AggregationBuilders.avg("avg_price").field("price"))
        );

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = this.client.search(searchRequest, RequestOptions.DEFAULT);

        // 获取所有的聚合
        Aggregations aggregations = searchResponse.getAggregations();

        // 根据聚合名称获取对应的聚合
        Terms makes = aggregations.get("makes");

        // 获取到所有的聚合分桶
        List<? extends Terms.Bucket> buckets = makes.getBuckets();

        buckets.forEach(bucket ->{

            // 由于参与聚合的key类型众多，所以只能是object
            String key = bucket.getKeyAsString();

            long count = bucket.getDocCount();

            // 同样地，这个返回的也是父接口 Aggregation 我们需要使用它的子接口 Avg 使用 getValue方法获取到具体的值
            Avg avg = bucket.getAggregations().get("avg_price");

            double avg_price = avg.getValue();

            System.out.println(key + "有" + count + "个，平均价格为：" + avg_price);

        });

    }

    @Test
    public void testAgg() throws IOException {

        SearchRequest searchRequest = new SearchRequest("car");

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 不展示查询结果
        sourceBuilder.size(0);

        // 添加聚合条件 桶内聚合和桶内度量 分了2个桶，外桶名称是 makes 再对里面的桶进行一个桶内度量和再进行一次桶内聚合
        // 桶内度量名称是 makes_colors，桶内聚合名称是 avg_price，这两个是并列关系
        sourceBuilder.aggregation(
                AggregationBuilders.terms("makes").field("make").size(10)
                .subAggregation(AggregationBuilders.terms("makes_colors").field("color"))
                .subAggregation(AggregationBuilders.avg("avg_price").field("price"))
        );

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = this.client.search(searchRequest, RequestOptions.DEFAULT);

        // 根据聚合名称获取对应的聚合结果
        Terms makes = searchResponse.getAggregations().get("makes");

        makes.getBuckets().forEach(bucket->{

            // 获取分桶的key
            String key = bucket.getKeyAsString();

            // 获取对应的数量
            long count = bucket.getDocCount();

            // 获取所有的子聚合
            Aggregations aggregations = bucket.getAggregations();

            // 从子聚合中获取名称为makes_colors的聚合分桶
            Terms makesColors = aggregations.get("makes_colors");

            // 从子聚合中获取名称为avg_price的聚合度量
            Avg avgPrice = aggregations.get("avg_price");

            double avg_price = avgPrice.getValue();

            System.out.println("桶" + key + "中有" + count + "个，平均价格为：" + avg_price);

            makesColors.getBuckets().forEach(subBucket ->{

                String subKey = subBucket.getKeyAsString();

                long subCount = subBucket.getDocCount();

                System.out.println("\t" + key + "的子桶中：" + subKey + "有" + subCount + "个");

            });

        });

    }

}
